#!/usr/bin/python2.4
#
#Copyright 2009 Google Inc.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
#

"""API to generate page tree.

Create a node from each metadata row. Each node contains a unique key and
an associated text value. It also maintains a list of its children and a
pointer to its parent. The entire page tree is stored in cache so that
it is easily accessible during the website generation step.
"""

import logging
import constants


class Node(object):
  """This class holds the node object in the page tree.

  Each node will contain a unique key and an associated text value. It will also
  maintain a list of its children and a pointer to its parent.
  """

  def __init__(self, key, value, parent_key, leaf_node):
    """The constructor.

    Args:
      key: The unique string identifier of this node.
      value: The string value (title) of this node.
      parent_key: String identifier of this node's parent.
      leaf_node: Boolean indicating whether the node is a leaf node.
    """
    self.key = key
    self.value = value
    self.children = []  # list of strings (child node keys)
    self.parent = parent_key
    self.leaf_node = leaf_node

  def AddChildNode(self, child_node_key):
    """Add a child node to this node.

    Args:
      child_node_key: The unique string identifier of the child node to be
      added.
    """
    if child_node_key not in self.children:
      self.children.append(child_node_key)

  def GetChildren(self):
    """Get all the child nodes for this node.

    Returns:
      A list of all the child node keys.
    """
    return self.children

  def GetKey(self):
    """Returns the key of this node.

    Returns:
      key Unique identifier of this node.
    """
    return self.key

  def GetValue(self):
    """Returns the value of this node.

    Returns:
      value The text value of this node.
    """
    return self.value

  def GetParent(self):
    """Returns the unique identifier of the parent of this node.

    Returns:
      parent key String identifier of this node's parent.
    """
    return self.parent

  def PrintNode(self):
    """Print this node."""
    logging.debug('NODE: ' + self.key + '@@@' + self.value + '@@@' + self.parent
                  + '@@@' + ''.join(self.children))


class PageTree(object):
  """This class is used to construct the page tree structure in memory.

  When a new Node is inserted in the tree, corresponding parent and child nodes
  are also created and a path is defined between them. This class is defined as
  Singleton and the page tree object is retained in memory throughout a
  session. The page tree object is created while reading the tsv file and is
  rendered at the time of page tree generation. The object is reinitialized
  while creating a new version of the website. (See CreateNewVersion() in
  db_utils.py.)
  """
  _pagetree = None

  class Singleton(object):
    """Class used with this Python singleton design pattern.

    Add all variables and methods needed for the Singleton class below.
    """

    def __init__(self):
      # TODO(shachi): parameterize the value for ROOT
      self._root_node = Node('ROOT', 'Home', '', False)
      self._node_map = {}
      self._node_map['ROOT'] = self._root_node

  def __init__(self):
    """The constructor."""
    # Check whether we already have an instance
    if PageTree._pagetree is None:
      # Create and remember the instance
      PageTree._pagetree = PageTree.Singleton()
    # Store instance reference as the only member in the handle
    self.__dict__['_PageTree_instance'] = PageTree._pagetree

  def __getattr__(self, attribute):
    """Delegate access to implementation.

    Args:
      attribute: Attribute wanted.

    Returns:
      Attribute value.
    """
    return getattr(self._pagetree, attribute)

  def __setattr__(self, attribute, value):
    """Delegate access to implementation.

    Args:
      attribute: Attribute wanted.
      value: Value to be set.

    Returns:
      Result of operation.
    """
    return setattr(self._pagetree, attribute, value)

  def ReplaceSpace(self, input_string):
    """Replace all spaces with underscores.

    Args:
      input_string: The input string possibly with spaces.

    Returns:
      A string with all spaces replaced with underscores.
    """
    return input_string.replace(' ', '_')

  def Clear(self):
    """Clears page tree instance data."""
    PageTree._pagetree = None

  def GetNode(self, node_key):
    """Returns the node object for the specified node key.

    Args:
      node_key: Unique string identifier for page tree node.

    Returns:
      node Node object for the specified node key.
    """
    if node_key in self._node_map:
      return self._node_map.get(node_key)
    else:
      return None

  def GetChildNodes(self, key):
    """Get all child nodes for a given key.

    Args:
      key: The key string for which the child nodes are to be fetched.

    Returns:
      A list of child nodes for the given key.
    """
    # list of child nodes
    children = []
    node = self._node_map.get(key)
    for child_key in node.GetChildren():
      child_node = self._node_map.get(child_key)
      children.append(child_node)
    return children

  def CreateNodes(self, levels, key, metadata, first_sorting_column,
                  second_sorting_column, third_sorting_column):
    """Create a new Node from the metadata and its parent and child nodes.

    Add these nodes to a node map containing (key, Node) pairs.

    Args:
      levels: Number of page tree levels.
      key: The unique string identifier of the content.
      metadata: Tab separated string containing the metadata values.
      first_sorting_column: String value of the Sorting Column 1.
      second_sorting_column: String value of the Sorting Column 2.
      third_sorting_column: String value of the Sorting Column 3.
    """
    key = str(key)
    # Generate all keys from leaf node to ROOT
    first_level_key = self.ReplaceSpace(first_sorting_column)
    second_level_key = first_level_key + '_'
    second_level_key += self.ReplaceSpace(second_sorting_column)
    if levels == 3:
      third_level_key = second_level_key + '_'
      third_level_key +=  self.ReplaceSpace(third_sorting_column)
    # Create corresponding nodes
    if levels == 3:
      leaf_node = Node(key, metadata, third_level_key, True)
      third_level_node = self._node_map.get(third_level_key)
      if not third_level_node:
        third_level_node = Node(third_level_key, third_sorting_column,
                                second_level_key, False)
    else:
      leaf_node = Node(key, metadata, second_level_key, True)
    second_level_node = self._node_map.get(second_level_key)
    if not second_level_node:
      second_level_node = Node(second_level_key, second_sorting_column,
                               first_level_key, False)
    first_level_node = self._node_map.get(first_level_key)
    if not first_level_node:
      first_level_node = Node(first_level_key, first_sorting_column, 'ROOT',
                              False)
    # Create path from leaf node to ROOT
    if levels == 3:
      third_level_node.AddChildNode(key)
      second_level_node.AddChildNode(third_level_key)
    else:
      second_level_node.AddChildNode(key)
    first_level_node.AddChildNode(second_level_key)
    self._root_node.AddChildNode(first_level_key)
    # Add all nodes to cache
    self._node_map[key] = leaf_node
    self._node_map[first_level_key] = first_level_node
    self._node_map[second_level_key] = second_level_node

    if levels == 3:
      self._node_map[third_level_key] = third_level_node

  def PrintPageTree(self, node=None):
    """Print the page tree for debugging purpose.

    It is a recursive function.
    It starts with the ROOT node and prints all its children till the child
    list is empty.

    Args:
      node: The current node to be printed. A Node instance.
    """
    if not node:
      node = self._root_node
      node.print_node()
    for child_key in node.GetChildren():
      logging.debug('Trying to retrieve: ' + child_key)
      child_node = self._node_map.get(child_key)
      child_node.PrintNode()
      self.PrintPageTree(node=child_node)

  def CreateJsonStructure(self, node=None, navigation_element={},
                          page_tree_element={}):
    """Create a json structure from the page tree object.

    Used for creating the collapsible tree navigation on the website.

    Args:
      node: The current node which is being json'ified. A Node instance.
      navigation_element: A map that maintains the entire json structure for
      navigation widget.
      page_tree_element: A map that maintains the entire json structure for the
      page tree.
    """
    if not node:
      node = self._root_node
      navigation_element['name'] = node.value.encode(constants.ENCODING)
      navigation_element['url'] = 'main.html'
      navigation_element['childNodes'] = []
      page_tree_element['name'] = node.value.encode(constants.ENCODING)
      page_tree_element['url'] = 'main.html'
      page_tree_element['childNodes'] = []
    for child_key in node.GetChildren():
      child_node = self._node_map.get(child_key)
      child_map_nav = {}
      child_map_page_tree = {}
      if child_node:
        if child_node.leaf_node:
          # For navigation json, only title column is added to the value.
          val = child_node.value.split(constants.FIELD_SEPARATOR)[0]
          # For page tree json, complete metadata is added as value.
          val_page_tree = child_node.value
        else:
          val = child_node.value
          val_page_tree = child_node.value
        child_map_nav['name'] = val.encode(constants.ENCODING)
        child_map_page_tree['name'] = val_page_tree.encode(constants.ENCODING)
      else:
        logging.error('no child node for key ' + child_key)
      # Create 'childNodes' array for non-leaf nodes. They should not have
      # 'url' attribute
      if child_node.GetChildren():
        child_map_nav['childNodes'] = []
        child_map_page_tree['childNodes'] = []
      # Create 'url' for leaf nodes
      child_map_nav['url'] = child_node.key.encode(constants.ENCODING) + '.html'
      child_map_page_tree['url'] = child_node.key.encode(constants.ENCODING)
      child_map_page_tree['url'] += '.html'
      navigation_element['childNodes'].append(child_map_nav)
      page_tree_element['childNodes'].append(child_map_page_tree)
      self.CreateJsonStructure(child_node, child_map_nav, child_map_page_tree)
